package com.example.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.dto.auth.Account;
import com.example.entity.vo.request.auth.EmailRegisterVO;
import com.example.entity.vo.request.auth.EmailResetVO;
import com.example.entity.vo.request.auth.ResetConfirmVO;
import com.example.mapper.AccountMapper;
import com.example.service.AccountService;
import com.example.utils.Const;
import com.example.utils.FlowUtil;
import jakarta.annotation.Resource;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 用户服务实现类，实现用户信息的查询与认证支持
 *
 * @auth shanyue
 * @date 2025年06月19日15:11
 */
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {

    @Resource
    private AmqpTemplate amqpTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private FlowUtil flowUtil;

    @Resource
    private PasswordEncoder passwordEncoder;

    /**
     * 根据用户名或邮箱加载用户信息
     *
     * @param username 用户名或邮箱
     * @return 用户详情
     * @throws UsernameNotFoundException 用户未找到异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = this.findAccountByNameOrEmail(username);
        if (account == null) {
            throw new UsernameNotFoundException("用户名或密码错误");
        }
        //构造UserDetails对象给SpringSecurity
        return User
                .withUsername(username)
                .password(account.getPassword())
                .roles(account.getRole())
                .build();
    }

    /**
     * 根据用户名或邮箱查找用户对象
     *
     * @param text 用户输入的用户名或邮箱
     * @return 数据库中的用户对象，没有则返回null
     */
    @Override
    public Account findAccountByNameOrEmail(String text) {
        return this.query()
                .eq("username", text)
                .or()
                .eq("email", text)
                .one();
    }

    /**
     * 生成并发送邮箱验证码，用于注册或密码重置
     * 通过IP进行限流，并将验证码通过RabbitMQ异步发送邮件
     * 同时存储Redis方便后续使用
     *
     * @param type  邮件类型
     * @param email 目标邮箱地址
     * @param ip    请求者的IP地址
     * @return null表示成功，否则返回错误信息
     */
    @Override
    public String registerEmailVerifyCode(String type, String email, String ip) {

        //同一IP同时只能一个线程请求
        synchronized (ip.intern()) {
            if (!this.verifyLimit(ip)) {
                return "请求频繁，请稍后再试";
            }

            //生成6位随机验证码
            Random random = new Random();
            int code = random.nextInt(899999) + 100000;

            //组装消息内容并发送至RabbitMQ mail队列
            Map<String, Object> data = Map.of("type", type, "email", email, "code", code);
            amqpTemplate.convertAndSend("mail", data);

            //将验证码存储Redis，有效时间3分钟
            stringRedisTemplate.opsForValue()
                    .set(Const.VERIFY_EMAIL_DATA + email, String.valueOf(code), 3, TimeUnit.MINUTES);
            return null;
        }

    }

    /**
     * 邮箱注册用户逻辑
     * 校验验证码是否正确，邮箱和用户是否被占用，验证通过后保存用户记录
     *
     * @param emailRegisterVO 注册请求参数对象
     * @return null表示注册成功，非null表示失败原因
     */
    @Override
    public String registerEmailAccount(EmailRegisterVO emailRegisterVO) {
        String username = emailRegisterVO.getUsername();
        String password = passwordEncoder.encode(emailRegisterVO.getPassword());
        String email = emailRegisterVO.getEmail();
        String key = this.buildVerifyKey(email);
        //从Redis中获取验证码
        String code = stringRedisTemplate.opsForValue().get(key);
        Account account = new Account(null, username, password, email, "USER", new Date());
        if (code == null) {
            return "请先获取验证码";
        } else if (!code.equals(emailRegisterVO.getCode())) {
            return "验证码输出错误，请重新改输入";
        } else if (this.existAccountBYEmail(email)) {
            return "此电子邮箱已被其他用户注册";
        } else if (this.existAccountBYUsername(username)) {
            return "请用户名已被其他用户注册，请更换一个新的用户名";
        }

        //注册成功则保存用户信息并删除验证码缓存
        if (this.save(account)) {
            stringRedisTemplate.delete(key);
            return null;
        } else {
            return "内部错误，请联系管理员";
        }

    }

    /**
     * 验证用户重置密码的验证码是否正确
     * @param resetConfirmVO    请求对象，包含邮箱和验证码
     * @return  null表示验证码通过，否则返回错误信息
     */
    @Override
    public String resetConfirm(ResetConfirmVO resetConfirmVO) {
        String email = resetConfirmVO.getEmail();
        String code = stringRedisTemplate.opsForValue().get(this.buildVerifyKey(email));
        if (code == null) {
            return "请先获取验证码";
        }
        if (!code.equals(resetConfirmVO.getCode())) {
            return "验证码错误，请重新输入";
        }
        return null;
    }

    /**
     * 根据邮箱重置用户密码
     * @param emailResetVO  重置密码请求参数对象
     * @return  null表示成功，否则返回错误信息
     */
    @Override
    public String resetEmailAccountPassword(EmailResetVO emailResetVO) {
        String email = emailResetVO.getEmail();
        String verify = this.resetConfirm(new ResetConfirmVO(email, emailResetVO.getCode()));
        if (verify != null) {
            return verify;
        }

        String password = passwordEncoder.encode(emailResetVO.getPassword());
        boolean update = this.update().eq("email", email).set("password", password).update();
        if (update) {
            stringRedisTemplate.delete(this.buildVerifyKey(email));
        }
        return null;
    }

    public Integer getAccountCount() {
        return Math.toIntExact(this.count());
    }

    /**
     * 检查当前IP是否符合验证码请求频率限制
     *
     * @param ip 请求者IP地址
     * @return true表示允许发送，false表示请求过于频繁
     */
    private boolean verifyLimit(String ip) {
        String key = Const.VERIFY_EMAIL_LIMIT + ip;
        return flowUtil.limitOnceCheck(key, 60);
    }

    /**
     * 检查邮箱是否已被注册
     *
     * @param email 待检查邮箱地址
     * @return true表示已存在，false表示未被注册
     */
    private boolean existAccountBYEmail(String email) {
        return this.baseMapper.exists(Wrappers.<Account>query().eq("email", email));
    }

    /**
     * 检查用户名是否已被注册
     *
     * @param username 待检查用户名
     * @return true表示已存在，false表示未被注册
     */
    private boolean existAccountBYUsername(String username) {
        return this.baseMapper.exists(Wrappers.<Account>query().eq("username", username));
    }

    /**
     * 构建邮箱验证码缓存键
     * @param email 邮箱地址
     * @return  Redis中使用的验证码键名
     */
    private String buildVerifyKey(String email){
        return Const.VERIFY_EMAIL_DATA + email;
    }

}
